package watch

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"gitee.com/xuender/oils/logs"
	"gitee.com/xuender/oils/sets"
	"gitee.com/xuender/reader/files"
	"gitee.com/xuender/reader/pb"
	"github.com/fsnotify/fsnotify"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"github.com/lithammer/shortuuid"
	"google.golang.org/protobuf/proto"
)

// Service 文件监听服务.
type Service struct {
	Paths    sets.Strings
	watcher  *fsnotify.Watcher
	upGrader websocket.Upgrader
	fs       *files.Service
	path2fid map[string]string
	clients  map[string]*client
	file2ids map[string]sets.Strings
}

// NewService 新建文件监听服务.
func NewService(
	fs *files.Service,
) *Service {
	w, err := fsnotify.NewWatcher()
	logs.Panic(err)

	return &Service{
		watcher:  w,
		fs:       fs,
		Paths:    sets.NewStrings(),
		path2fid: map[string]string{},
		file2ids: map[string]sets.Strings{},
		clients:  map[string]*client{},
		upGrader: websocket.Upgrader{
			CheckOrigin: func(r *http.Request) bool {
				return true
			},
		},
	}
}

// Close 关闭.
func (p *Service) Close() {
	p.watcher.Close()
}

func (p *Service) hasPath(path string) bool {
	for s := range p.Paths {
		s = fmt.Sprintf("%s%c", s, os.PathSeparator)
		if strings.HasPrefix(path, s) {
			return true
		}
	}

	return false
}

// Add 新增监听目录.
func (p *Service) Add(paths ...string) {
	for _, s := range paths {
		path, err := filepath.Abs(s)
		if err != nil {
			continue
		}

		if p.Paths.Has(path) {
			continue
		}

		if p.hasPath(path) {
			continue
		}

		h := fmt.Sprintf("%s%c", path, os.PathSeparator)
		for s := range p.Paths {
			if strings.HasPrefix(s, h) {
				p.Paths.Del(s)
			}
		}

		p.Paths.Add(path)
	}
}

func (p *Service) pathID(path string) string {
	id, has := p.path2fid[path]

	if !has {
		id = files.Hash(path)
		p.path2fid[path] = id
	}

	return id
}

// Run 运行.
func (p *Service) Run() {
	for path := range p.Paths {
		_ = p.watcher.Add(path)
	}

	for {
		select {
		case event, ok := <-p.watcher.Events:
			if !ok {
				return
			}

			if event.Op&fsnotify.Write == fsnotify.Write {
				fid := p.pathID(event.Name)
				did := p.pathID(filepath.Dir(event.Name))

				p.fs.DelFolder(did)
				logs.Debug("文件修改:", event.Name, fid)

				if set, has := p.file2ids[fid]; has {
					for id := range set {
						logs.Debug("文件修改:", id)
						p.write(id, fid)
					}
				}
			}
		case err, ok := <-p.watcher.Errors:
			logs.Error("error:", err, ok)
		}
	}
}

func (p *Service) del(id string, ws *websocket.Conn) {
	if c, has := p.clients[id]; has {
		if set, h := p.file2ids[c.fid]; h {
			set.Del(c.fid)
		}

		delete(p.clients, id)
	}

	ws.Close()
	logs.Debug("close:", id)
}

// Websocket ws.
func (p *Service) Websocket(ctx *gin.Context) {
	var id string

	ws, err := p.upGrader.Upgrade(ctx.Writer, ctx.Request, nil)
	if err != nil {
		return
	}

	defer p.del(id, ws)

	for {
		mt, message, err := ws.ReadMessage()
		if err != nil {
			logs.Error(err)

			break
		}

		if mt != websocket.BinaryMessage {
			logs.Warn("未知的消息", string(message))

			continue
		}

		msg := &pb.Msg{}
		if err := proto.Unmarshal(message, msg); err != nil {
			logs.Error(err)

			continue
		}

		switch msg.Type {
		case pb.MsgType_init:
			id = p.wsInit(msg, ws)
		case pb.MsgType_open:
			p.wsOpen(id, msg, ws)
		case pb.MsgType_close:
			p.wsClose(id, msg)
		case pb.MsgType_write:
		}
	}
}

func (p *Service) wsClose(id string, msg *pb.Msg) {
	if set, has := p.file2ids[msg.Id]; has {
		set.Del(id)
	}

	if c, has := p.clients[id]; has {
		c.setFile("", 0)
	}

	logs.Debug("close:", msg.Id, "id:", id)
}

func (p *Service) wsInit(msg *pb.Msg, ws *websocket.Conn) string {
	id := shortuuid.New()
	msg.Id = id
	p.clients[id] = newClient(id, ws)
	p.send(ws, msg)
	logs.Debug("init:", id)

	return id
}

func (p *Service) wsOpen(id string, msg *pb.Msg, ws *websocket.Conn) {
	if set, has := p.file2ids[msg.Id]; has {
		set.Add(id)
	} else {
		p.file2ids[msg.Id] = sets.NewStrings(id)
	}

	if c, has := p.clients[id]; has {
		c.setFile(msg.Id, msg.Interval)
	} else {
		c := newClient(id, ws)

		c.setFile(msg.Id, msg.Interval)
		p.clients[id] = c
	}

	logs.Debug("open:", msg.Id, "id:", id)
}

func (p *Service) send(ws *websocket.Conn, msg *pb.Msg) {
	bs, err := proto.Marshal(msg)
	if err != nil {
		logs.Error("序列化", err)

		return
	}

	if err := ws.WriteMessage(websocket.BinaryMessage, bs); err != nil {
		logs.Error("发送消息", err)
	}
}

func (p *Service) write(id, fid string) {
	c, has := p.clients[id]
	if !has {
		logs.Error("ws 未找到:", id)

		return
	}

	if fid != c.fid || c.fid == "" || c.interval == 0 {
		return
	}

	switch c.state {
	case first:
		c.state = noSleep
		p.send(c.ws, &pb.Msg{
			Type: pb.MsgType_write,
			Id:   fid,
		})
	case noSleep:
		c.state = sleep

		go func() {
			d := time.Until(c.last.Add(time.Second * time.Duration(c.interval)))
			if d > 0 {
				time.Sleep(d)
			}

			p.send(c.ws, &pb.Msg{
				Type: pb.MsgType_write,
				Id:   fid,
			})

			c.last = time.Now()
			c.state = noSleep
		}()
	case sleep:
	}
}
